using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using ArkanoidX.Gums;

namespace ArkanoidX.Objects
{
    /// <summary>
    /// This is a _game component that implements IUpdateable.
    /// </summary>
    public class Bricks : BaseComponent
    {
        /*
         * the number of bricks for each line is fixed as 11
         */
        public const int _kBricksPerLine = 11;
        public const int _MaxLines = 11;

        public Rectangle _screenBounds;

        public List<Brick> _bricks = new List<Brick>();

        public Layer _layer;


        /**
         * brick collection properties
         */
        public Vector2 _brickSpaceSize;

        public const int _gumRate = 25;

        public Level Level
        {
            get;
            set;
        }

        public Bricks(Game game, Rectangle screenBounds)
            : base(game)
        {
            _screenBounds = screenBounds;

            // TODO: Construct any child components here
            _layer = ((MainGame)game).BricksLayer;
        }

        public Vector2 BrickSize
        {
            get
            {
                return _brickSpaceSize;
            }
        }

        /// <summary>
        /// Allows the _game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // TODO: Add your initialization code here

            /**
             * <remarks>
             *  we can do the initialization here
             *  or we can set up a flag to control the initialization
             *  performed in Update method
             * </remarks>
             */
            foreach (Brick brick in _bricks)
            {
                /**
                 * this is the right place for objects to be added to the game 
                 * component collection when the game is guaranteed to have been
                 * initialized 
                 */
                brick.AddToLayer(_layer);
                if (brick.Gum != null)
                {
                    brick.Gum.AddToLayer(_layer);
                }
            }

            base.Initialize();
        }

        /// <summary>
        /// Allows the _game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here

            if (this._bricks.Count == 0)
            {
                this.Level.Finish();
            }

            base.Update(gameTime);
        }

        public void LoadFromString(String brickString, int numOfRows, int brickXStart, int brickYStart)
        {
            ConfigParser cp = new ConfigParser(brickString);

            float x = _screenBounds.Left;
            float y = _screenBounds.Top;
            /**
             * brick width is determined by the screen width
             */
            float brickSpaceWidth = (float)Math.Round((double)_screenBounds.Width / _kBricksPerLine);
            float brickSpaceHeight = brickSpaceWidth * 0.3f;

            float brickWidth = brickSpaceWidth * 0.95f;
            float brickHeight = brickSpaceHeight * 0.90f;
            float brickHalfSpaceWidth = brickSpaceWidth * 0.5f;
            float brickHalfSpaceHeight = brickSpaceHeight * 0.5f;

            _brickSpaceSize = new Vector2(brickSpaceWidth, brickSpaceHeight);

            Gum.InstallTextures(this.Game);

            Random rand = new Random();


            Dictionary<char, Brick.State[]> brickMap = new Dictionary<char, Brick.State[]>();
            brickMap.Add('R',
                new Brick.State[] {
                new Brick.NormalState(this.Game.Content.Load<Texture2D>("brickRed"), 
                    new Vector2(brickWidth, brickHeight))}
            );

            brickMap.Add('G',
                new Brick.State[] {
                new Brick.NormalState(this.Game.Content.Load<Texture2D>("brickGreen"),
                    new Vector2(brickWidth, brickHeight))}
            );

            brickMap.Add('B',
                new Brick.State[] {
                new Brick.NormalState(this.Game.Content.Load<Texture2D>("brickBlue"),
                    new Vector2(brickWidth, brickHeight))}
            );

            // set to extreme values to accept initial values from assignment
            int upperBound = int.MaxValue;
            int lowerBound = int.MinValue;
            int leftBound = int.MaxValue;
            int rightBound = int.MinValue;

            /**
             * skip brickYStart rows
             */
            y += brickSpaceHeight * brickYStart;
            x += brickSpaceWidth * brickXStart;

            for (int i = 0; i < numOfRows; i++ )
            {
                String line = cp.ReadLine();
                if (line == null)
                {
                    break;
                }
                for (int j = 0; j < line.Length; j++)
                {
                    char c = line[j];
                    if (c != ' ')
                    {
                        if (!brickMap.Keys.Contains<char>(c))
                        {
                            c = 'R';
                        }
                        if (j < leftBound) leftBound = j;
                        if (j > rightBound) rightBound = j;
                        if (i < upperBound) upperBound = i;
                        if (i > lowerBound) lowerBound = i;

                        Brick.State[] states = brickMap[c];

                        Gum gum = null;
                        if (rand.Next(100) < _gumRate)
                        {
                            gum = Gum.CreateARandomGum(this.Game, rand, this);
                        }

                        Brick brick = new Brick(this.Game, states,
                            new Vector2(x + brickHalfSpaceWidth, y + brickHalfSpaceHeight),
                            this, gum);

                        this._bricks.Add(brick);
                    }
                    x += brickSpaceWidth;
                }
                y += brickSpaceHeight;
                x = _screenBounds.Left + brickSpaceWidth * brickXStart;
            }
        }

        public void Collides(IPhysicalObject phyObj)
        {
            if (phyObj is Ball || phyObj is Shots.Shot)
            {
                Ball ball = phyObj as Ball;
                Shots.Shot shot = phyObj as Shots.Shot;
                
                // TODO: pre-judgement
                // ...

                for (int i = 0; i < this._bricks.Count; )
                {
                    Brick brick = this._bricks[i];

                    bool collided = false;
                    
                    if (ball != null && brick.CollidesWithBall(ball))
                    {
                        // play sound
                        ((MainGame)Game).SoundResources.PlayKnock();

                        collided = true;
                    }
                    else if (shot != null && brick.CollidesWithShot(shot))
                    {
                        collided = true;
                        shot.Perish();
                    }

                    if (collided)
                    {   // remove brick
                        foreach (GameComponent component in this.Game.Components)
                        {
                            if (component == brick)
                            {
                                brick.RemoveFromGame();
                                break;
                            }
                        }

                        _bricks.RemoveAt(i);

                        Level.Score += brick.Worth;
                        
                        // the brick is about to be eliminated
                        if (brick.Gum != null)
                        {
                            brick.Gum.Start();
                        }
                    }
                    else
                    {
                        i++;
                    }
                }
            }
        }   /* Collides */
    }   /* class Bricks */
}
